#include "qe_log.h"
#include "qe_string.h"
#include "qe_memory.h"
#include "qe_assert.h"
#include "qe_driver.h"
#include "libfdt.h"



QELOG_DOMAIN("axi-gpio");



typedef struct
{
    volatile qe_u32 data;
    volatile qe_u32 control;
} axi_gpio_reg;

typedef struct
{
    qe_gpio_dev gpio;
    axi_gpio_reg *reg;
    qe_uint num_pins;
} axi_gpio_dev;

static qe_ret axi_gpio_direction_input(qe_gpio_dev *gpio, qe_uint pin)
{
    axi_gpio_dev *dev = (axi_gpio_dev *)gpio;

    if (pin >= dev->num_pins) {
        qe_error("pin:%d out of range", pin);
        return qe_err_range;
    }

    dev->reg->control |= (1 << pin);

    qe_debug("%s pin:%d set input", gpio->parent.name, pin);

    return qe_ok;
}

static qe_ret axi_gpio_direction_output(qe_gpio_dev *gpio, qe_uint pin, qe_int value)
{
    axi_gpio_dev *dev = (axi_gpio_dev *)gpio;

    if (pin >= dev->num_pins) {
        qe_error("pin:%d out of range", pin);
        return qe_err_range;
    }

    dev->reg->control &= ~(1 << pin);

    if (value) {
        dev->reg->data |= (1 << pin);
    } else {
        dev->reg->data &= ~(1 << pin);
    }

    qe_debug("pin:%d set output %d", pin, value);

    return qe_ok;
}

static qe_int axi_gpio_get_value(qe_gpio_dev *gpio, qe_uint pin)
{
    qe_int value;
    axi_gpio_dev *dev = (axi_gpio_dev *)gpio;

    if (pin >= dev->num_pins) {
        qe_error("pin:%d out of range", pin);
        return 0;
    }

    value = (dev->reg->data >> pin) & 1;

    qe_debug("pin:%d get val:%d", pin, value);

    return value;
}

static qe_ret axi_gpio_set_value(qe_gpio_dev *gpio, qe_uint pin, qe_int value)
{
    axi_gpio_dev *dev = (axi_gpio_dev *)gpio;

    if (pin >= dev->num_pins) {
        qe_error("pin:%d out of range", pin);
        return 0;
    }

    if (value) {
        dev->reg->data |= (1 << pin);
    } else {
        dev->reg->data &= ~(1 << pin);
    }

    qe_debug("pin:%d set val:%d", value);

    return qe_ok;
}

static void axi_gpio_show(qe_gpio_dev *dev)
{
    axi_gpio_dev *gpio = (axi_gpio_dev *)dev;

    qe_printf("name     : %s\r\n", dev->parent.name);
    qe_printf("regs     : %x\r\n", gpio->reg);
    qe_printf("num pins : %x\r\n", gpio->num_pins);
}

static void axi_gpio_dump_regs(qe_gpio_dev *dev)
{
    axi_gpio_dev *gpio = (axi_gpio_dev *)dev;

    qe_printf("data    : %x\r\n", gpio->reg->data);
    qe_printf("control : %x\r\n", gpio->reg->control);
}

static void axi_gpio_list_pins(qe_gpio_dev *dev)
{
    int i;
    qe_const_str head_fmt = "%-5s%-12s%-5s\r\n";
    qe_const_str line_fmt = "%-5d%-12s%-5d\r\n";
    axi_gpio_dev *gpio = (axi_gpio_dev *)dev;

    qe_printf(head_fmt, "pin", "direction", "value");
    for (i=0; i<gpio->num_pins; i++) {
        if (gpio->reg->control & (1 << i)) {
            qe_printf(line_fmt, i, "in", gpio->reg->data & (1 << i));
        } else {
            qe_printf(line_fmt, i, "out", gpio->reg->data & (1 << i));
        }
    }
}

static qe_gpio_ops axi_gpio_ops = {
    .direction_input = axi_gpio_direction_input,
    .direction_output = axi_gpio_direction_output,
    .get_value = axi_gpio_get_value,
    .set_value = axi_gpio_set_value,
    .show = axi_gpio_show,
    .dump_regs = axi_gpio_dump_regs,
    .list_pins = axi_gpio_list_pins,
};

static qe_ret axi_gpio_probe(const void *fdt, int offset)
{
	int len;
    const char *name;
    const qe_u32 *prop_reg;
    const qe_u32 *prop_num_pins;
    axi_gpio_dev *dev;

    name = fdt_get_name(fdt, offset, QE_NULL);

    prop_reg = (const qe_u32 *)fdt_getprop(fdt, offset, "reg", &len);
    if (!prop_reg) {
        qe_error("%s no reg prop", name);
        return qe_err_param;
    }

    prop_num_pins = (const qe_u32 *)fdt_getprop(fdt, offset, "num-pins", &len);
    if (!prop_num_pins) {
        qe_error("no num-pins prop", name);
        return qe_err_param;
    }

    dev = qe_malloc(sizeof(axi_gpio_dev));
    qe_assert(dev);

    dev->reg = (axi_gpio_reg *)(qe_ubase)fdt32_to_cpu(*prop_reg);
    dev->num_pins = fdt32_to_cpu(*prop_num_pins);

    qe_gpio_register(&dev->gpio, name, &axi_gpio_ops, dev);

    qe_debug("register %s", name);

    return qe_ok;
}

static const qe_device_id axi_gpio_ids[] = {
    {.compatible = "xlnx,axi-gpio",},
    {}
};

QE_DRIVER(axi_gpio) = {
    .name = "axi_gpio",
    .of_match = axi_gpio_ids,
    .probe = axi_gpio_probe,
};

QE_DRIVER_FORCE_EXPORT(axi_gpio);
